"""Functions for computing field of view"""

from pyre.core import types
from pyre.view import los, process

def raycast(world, origin, radius):
    """
    raycast(World, Coordinate, int): set of Coordinate
    Uses Bresenham's line drawing algorithm to cast rays to every edge of a
    square with the given radius in order to compute line of sight
    """

    fov = set()
    for xpos in range(origin[0] - radius, origin[0] + radius + 1):
        fov.update(los.bresenham(world, origin, (xpos, origin[1] + radius)))
        fov.update(los.bresenham(world, origin, (xpos, origin[1] - radius)))
    for ypos in range(origin[1] - radius, origin[1] + radius + 1):
        fov.update(los.bresenham(world, origin, (origin[0] + radius, ypos)))
        fov.update(los.bresenham(world, origin, (origin[0] - radius, ypos)))
    return fov


def _compute_table(radius):
    table = {(0, 0): {(1, 0), (1, 1)}}

    curr_brake = 0
    break_count = 0

    for x in range(1, radius):
        next_y = 0
        for y in range(x + 1):
            if y == curr_brake:
                table[x, y] = {(x + 1, next_y), (x + 1, next_y + 1)}
                next_y += 2
            else:
                table[x, y] = {(x + 1, next_y)}
                next_y += 1
        break_count -= 1
        if break_count < 0:
            break_count = curr_brake + 1
            curr_brake += 1

    for y in range(radius + 1):
        table[radius, y] = set()

    def update(pos, trans):
        if pos in table:
            table[pos].update(trans)
        else:
            table[pos] = trans

    for x, y in table.keys():
        update((y, x), {(b, a) for a, b in table[x, y]})
    for x, y in table.keys():
        update((-x, y), {(-a, b) for a, b in table[x, y]})
        update((-x, -y), {(-a, -b) for a, b in table[x, y]})
        update((x, -y), {(a, -b) for a, b in table[x, y]})

    return table


def shadowcast(world, origin, radius, _cache={}):
    """
    shadowcast(World, Coordinate, int): set of Coordinate
    Uses a precomputed dependency graph to very quickly compute a shadowcasting
    field of view.
    """

    if radius not in _cache:
        _cache[radius] = _compute_table(radius)
    table = _cache[radius]

    origin = types.Coordinate.copy(origin)
    fov = {origin}
    stack = {(0, 0)}

    while stack:
        offset = stack.pop()
        pos = origin + offset
        fov.add(pos)
        if world.translucient_at(pos):
            stack.update(table[offset])

    return fov


def default(world, origin, radius):
    """
    default(World, Coordinate, int): set of Coordinate
    The default field of view algorithm in Pyre is a circular shadowcast with
    artifact removal.
    """

    fov = shadowcast(world, origin, radius)
    process.fix_wall(fov, origin, radius)
    process.circular(fov, origin, radius)
    return fov
